/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.datasource;

import com.jolbox.bonecp.BoneCPDataSource;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jrose.api.config.ConfigManager;
import org.jrose.api.config.DataSourceConfig;
import org.jrose.core.util.BeanFactoryProvider;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

import javax.sql.DataSource;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
@NoArgsConstructor
public class RoutingDataSourceFactoryBean extends BeanFactoryProvider implements FactoryBean<DataSource>, InitializingBean, DisposableBean, BeanClassLoaderAware {
    @Setter private ClassLoader beanClassLoader;

    @Setter private ConfigManager configManager;
    @Setter private String masterDataSourceName;
    @Setter private String slaveDataSourceNames;

    private final Set<BoneCPDataSource> closeCandidates = new HashSet<>();
    private RoutingDataSource routingDataSource;

    @Override
    public void afterPropertiesSet() throws Exception {
        Validate.notNull(configManager, "'configManager' no defined");
        Validate.notBlank(masterDataSourceName, "'masterDataSourceName' no defined");
        if (slaveDataSourceNames == null) slaveDataSourceNames = "";

        DataSourceConfig masterDataSourceConfig = configManager.getDataSourceConfig(masterDataSourceName);
        Validate.notNull(masterDataSourceConfig, "Data source '%s' not found in config manager", masterDataSourceName);
        BoneCPDataSource master = createDataSource(masterDataSourceConfig);
        closeCandidates.add(master);

        Map<Object, Object> slaves = new LinkedHashMap<>();

        String[] slaveNames = StringUtils.split(slaveDataSourceNames, ",");
        for (int i = 0; i < slaveNames.length; i++) {
            String slaveDataSourceName = slaveNames[i];
            DataSourceConfig slaveDataSourceConfig = configManager.getDataSourceConfig(slaveDataSourceName);
            Validate.notNull(slaveDataSourceConfig, "Data source '%s' not found in config manager", slaveDataSourceName);
            BoneCPDataSource slave = createDataSource(slaveDataSourceConfig);
            closeCandidates.add(slave);

            slaves.put("slave#" + i, slave);
        }

        routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(master);
        routingDataSource.setTargetDataSources(slaves);
        routingDataSource.afterPropertiesSet();
    }

    @Override
    public void destroy() throws Exception {
        for (BoneCPDataSource dataSource : closeCandidates) {
            if (dataSource != null) {
                dataSource.close();
            }
        }
    }

    @Override
    public DataSource getObject() throws Exception {
        return routingDataSource;
    }

    @Override
    public Class<?> getObjectType() {
        return DataSource.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    private BoneCPDataSource createDataSource(DataSourceConfig config) {
        String driverClass = config.getDriverClass();
        String jdbcUrl = config.getJdbcUrl();
        try {
            beanClassLoader.loadClass("net.sf.log4jdbc.DriverSpy");
            driverClass = "net.sf.log4jdbc.DriverSpy";
            jdbcUrl = StringUtils.replace(jdbcUrl, "jdbc:", "jdbc:log4jdbc:");
        } catch (ClassNotFoundException ex) {
            // no log4jdbc-remix available, ignore
        }

        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setDriverClass(driverClass);
        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUsername(config.getUsername());
        dataSource.setPassword(config.getPassword());
        dataSource.setPartitionCount(config.getPartitionCount());
        dataSource.setMinConnectionsPerPartition(config.getMinConnectionsPerPartition());
        dataSource.setMaxConnectionsPerPartition(config.getMaxConnectionsPerPartition());
        dataSource.setAcquireIncrement(config.getAcquireIncrement());
        dataSource.setPoolAvailabilityThreshold(config.getPoolAvailabilityThreshold());
        dataSource.setConnectionTimeoutInMs(config.getConnectionTimeoutInMs());
        dataSource.setReleaseHelperThreads(config.getReleaseHelperThreads());
        dataSource.setStatementReleaseHelperThreads(config.getStatementReleaseHelperThreads());
        dataSource.setIdleConnectionTestPeriodInMinutes(config.getIdleConnectionTestPeriodInMinutes());
        dataSource.setIdleMaxAgeInMinutes(config.getIdleMaxAgeInMinutes());
        dataSource.setStatementsCacheSize(config.getStatementsCacheSize());
        dataSource.setConnectionTestStatement(config.getConnectionTestStatement());
        return dataSource;
    }
}
